/*
* ========== Copyright Header Begin ==========================================
* 
* OpenSPARC T1 Processor File: execkern.c
* Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
* 
* The above named program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License version 2 as published by the Free Software Foundation.
* 
* The above named program is distributed in the hope that it will be 
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
* 
* You should have received a copy of the GNU General Public
* License along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
* 
* ========== Copyright Header End ============================================
*/
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"@(#)execkern.c	1.30	06/06/12 SMI"

	/*
	 * Execution kernel for the simualtor.
	 *
	 * Basic management of simcpu_t's and core simualtor
	 * instruction set.
	 */


	/*
	 *	#     #
	 *	##    #   ####    #####  ######
	 *	# #   #  #    #     #    #
	 *	#  #  #  #    #     #    #####
	 *	#   # #  #    #     #    #
	 *	#    ##  #    #     #    #
	 *	#     #   ####      #    ######
 	 *
	 * If you think you need to edit this file - in particular
	 * the exec_loop function - you are probably wrong. 
	 * exec_loop is the most performance critical
	 * function in the simulator, and is fully processor
	 * independent. 
	 */


#include <assert.h>



#include "basics.h"
#include "simcore.h"
#include "config.h"
#include "xicache.h"
#include "barrier.h"
#include "fatal.h"


extern barrier_t stop_barrier;
extern barrier_t run_barrier;
extern barrier_busy_t sync_busy_barrier;

extern volatile bool_t running;


#if DEBUG_HOOK	/* { */
void    (*debug_hookp)(simcpu_t *sp, uint32_t rawi);
#endif /* } DEBUG_HOOK */


	/*
	 * Some pre-amble has to set up the xicache pointer, but
	 * just execute from it ...
	 * ... assume the next function to execute has already been
	 * prefetched ... which in the case of a trap/alarm etc.
	 * is not the actual instruction ...
	 *
	 * Probably should replace this code with assembler for
	 * efficiency !
	 *
	 * This loop is called by each exec_thread and is responsible
	 * for scheduling all the simcpus for that exec_thread to
	 * ensure they make equal progress. We allow the first simcpu
	 * to run for an EXEC_QUANTUM of instructions. We use the
	 * target_match functionality to detect when the EXEC_QUANTUM
	 * is complete and then we move to the next simcpu in the list
	 * until we reach the end of the list.
	 *
	 * Once an exec_thread has executed each of it's simcpus for
	 * an EXEC_QUANTUM, it will wait for all other exec_threads
         * to reach that same point. Once all exec_threads have reached
	 * this point, they all continue scheduling their simcpus
	 * starting with the head of the list again.
	 */

void exec_loop(exec_thread_t *etp)
{
	simcycle_t prev_cycle;
	xicache_t *xicp;
	uint64_t xic_line_tag;
	simcpu_t *sp;
	xicache_line_t *xic_linep;
	uint64_t	memoryoffset;
	simcpu_t	*nextsp;

	sp = etp->allp;

top:

	if (!sp->runnable) {
		sp->cycle += EXEC_QUANTUM;
		goto next_simcpu;
	}

	prev_cycle = sp->cycle;

	xicp = sp->xicachep;

#if DEBUG_HOOK	/* { */
	debug_hookp = (void(*)(simcpu_t *, uint32_t))
		sp->config_procp->proc_typep->debug_hookp;
#endif	/* } DEBUG_HOOK */

	do {	/* outer loop */

		/* force a miss of the current xic block */
		goto xic_block_load;

		do {
			uint64_t xidx;
			uint32_t rawi;
			tvaddr_t xpc;
			xicache_instn_t *xip;

				/*
				 * This is the dumb way to do this, but we
				 * mirror the hardware, and check that
				 * we're still on the xic block we thought
				 * we were .. if not re-validate and try
				 * again.
				 */
			xpc = sp->pc;

			/* tag and align ok ? */
			if (((xpc ^ xic_line_tag) & XICACHE_TAG_MASK) != 0) {

xic_block_load:;
				/*
				 * Fell off XC block - maybe:
				 * a) moved to another line ...
				 * b) problem with pc alignment !
				 * c) page-fault (/cache miss)
				 * d) xic_cache_miss
				 *
				 * First check the xic cache as this is
				 * the fast case.
				 */

				/*
				 * assume we just moved to another line ...
				 * if not it's a miss of some kind
				 */

				xpc = sp->pc;
				xic_line_tag = xpc & XICACHE_TAG_MASK;

					/*
					 * Need to stop using hard coded
					 * constants here ...  and use
					 * variable values - but how to do
					 * without performance hit ...
					 */

				xic_linep = &(xicp->line[(xic_line_tag >> XICACHE_LINE_SIZE_BITS)&XICACHE_LINE_MASK]);
				if (xic_linep->tag != xic_line_tag) {
					sp->xic_miss(sp, xic_linep, sp->pc);
					if (sp->attention) goto pay_attention;

					/*
					 * Go because pc may have changed
					 * or XC flushed!
					 */
					goto xic_block_load;
				}
				memoryoffset = xic_linep->memoryoffset;
			}

			if (sp->cycle >= sp->cycle_target) {
				sp->cycle_target_match(sp);

				/*
				 * Maybe an exeception before instn
				 * execution?
				 */
				if (sp->attention) goto pay_attention;
			}

			xidx = (xpc>>2) & XICACHE_NUM_INSTR_MASK;
			xip = &xicp->instn[xidx];
			rawi = *(uint32_t *)(void*)(xpc + memoryoffset);
			if (xip->rawi != rawi) {
				xip->rawi = rawi;
				xip->exec_funcp = sp->decodemep;
			}

#if DEBUG_HOOK /* { */
			debug_hookp(sp, xip->rawi);
#endif /* } DEBUG_HOOK */
			xip->exec_funcp(sp, xip);

			/*
			 * Only get here after successful execution of an
			 * instn.
			 */
			sp->cycle++;

		} while (!sp->attention);

pay_attention:;

		/*
		 * First thing we do is clear the attention flag before we
		 * possibly clear any of the flags indicating a reason.
		 *
		 * NB: for any parallel entity setting these flags, they
		 * *must* set the reason *before* setting the attention
		 * flag to avoid a reason being lost as a result of the
		 * attention flag being clear here ... of course we assume
		 * host TSO/PSO.
		 *
		 * This doesn't matter for our own thread setting flags,
		 * but for others the above must hold.
		 */
		sp->attention = false;

		/*
		 * So many reasons for attention ...
		 * ... e.g. the debugger stopped us for some reason.
		 */

		/* first check if an async event changed our state */
		if (sp->async_event) {
			sp->config_procp->proc_typep->check_async_event(sp);
		}

		/* then check if any pending event changes our state */
		if (sp->exception_pending) {
			sp->config_procp->proc_typep->take_exception(sp);
		}

	} while (!sp->sync_pending);

	/*
	 * We fall out of the loop above when sp->sync_pending is set.
	 * This happens when the current simcpu has executed it's
	 * EXEC_QUANTUM of instns and it's time for the exec_thread
	 * to schedule the next simcpu in the list -or- if we are
	 * at the end of the list, wait for all the other
	 * exec_threads to reach the same point.
	 */

	sp->sync_pending = false;

	/*
	 * increment the total instruction count executed during this
	 * EXEC_QUANTUM cycle
	 */
	sp->total_instr += sp->cycle - prev_cycle;

next_simcpu:;

	/* Switch to next sp in scheduler list if we are not at the end */
	nextsp = sp->nextp;
	if (nextsp != NULL) {
		sp = nextsp;
		goto top;
	}

	/*
	 * We have reached the end of the scheduler list.  All simcpus on
	 * this exec_thread have have a chance to execute an EXEC_QUANTUM
	 * of instructions so we wait here until all exec_threads reach
	 * this point before continuing.
	 */
	barrier_busy_wait(&sync_busy_barrier);

	/*
	 * We need to check if the debugger wants us to stop running.
	 * If so, we put hold all threads here. The ctrl_thread
	 * will handle the "stop" state housekeeping.
	 */
	if (running == false) {
		/* indicate exec_thread in stop state */
		barrier_wait(&stop_barrier);

		/* wait to enter run state (via ctrl thread) */
		barrier_wait(&run_barrier);
	}

	/*
	 * Give everyone a chance to check the value of "running" before we
	 * make another round.  Otherwise can split up -- some will go to
	 * "stop" state while others will run another EXEC_QUANTUM.
	 */
	barrier_busy_wait(&sync_busy_barrier);

	/*
	 * All exec_threads have reached the same point so we start
	 * at the begining of the list of simcpus and continue
	 * executing.
	 */
	sp = sp->headp;
	goto top;

}
